#include "RenderMan.h"
#include <iostream>

void RenderMan::Slow_DrawLine(INT32 x1, INT32 y1, INT32 x2, INT32 y2, UINT32 color)
{
	if (abs(x2-x1) >= abs(y2-y1))
	{
		if (x1 > x2) 
		{
			std::swap(x1, x2);
			std::swap(y1, y2);
		}
		float x_v = x2 - x1;
		float y_v = y2 - y1;
		float a = y_v/x_v;
		for (INT32 i=x1; i<=x2; i++) {
			m_vScreenBuffer[(roundftoi((i-x1)*a) + y1)*m_iWidth + i] = color;
		}
	}
	else
	{
		if (y1 > y2) 
		{
			std::swap(x1, x2);
			std::swap(y1, y2);
		}
		float x_v = x2 - x1;
		float y_v = y2 - y1;
		float a = x_v/y_v;
		for (INT32 i=y1; i<=y2; i++) {
			m_vScreenBuffer[i*m_iWidth + x1+roundftoi((i-y1)*a)] = color;
		}
	}
}

RenderMan::RenderMan()
{
}

RenderMan::~RenderMan()
{
	Shutdown();
}

void RenderMan::Initialize(HWND hWnd, UINT32 iWidth, UINT32 iHeight)
{
	std::clog << "Initializing render manager..." << std::endl;
	m_hWnd = hWnd;
	m_iWidth = iWidth;
	m_iHeight = iHeight;
	m_dwByteSize = iWidth * iHeight * (_BITS_PER_PIXEL >> 3);
	m_dwPixelCount = iWidth * iHeight;
	
	m_vScreenBuffer.resize(m_dwPixelCount);
	m_vScreenBuffer.assign(m_dwPixelCount, 0);
	
	m_vZBuffer.resize(m_dwPixelCount);
	m_vZBuffer.assign(m_dwPixelCount, 0);
	
	m_iTextBufferHeight = 64;
	m_iTextBufferWidth = 256;
	
	UINT32 iTextByteSize = m_iTextBufferHeight*m_iTextBufferWidth*(_BITS_PER_PIXEL >> 3);
	m_vTextBuffer.resize(m_iTextBufferHeight*m_iTextBufferWidth);
	m_vTextBuffer.assign(m_vTextBuffer.size(), 0);

	m_hDrawDib = DrawDibOpen();

	memset(&m_bih, 0, sizeof(BITMAPINFOHEADER));
	m_bih.biSize = sizeof(BITMAPINFOHEADER);
	m_bih.biWidth = m_iWidth;
	m_bih.biHeight = m_iHeight;
	m_bih.biPlanes = 1;
	m_bih.biBitCount = _BITS_PER_PIXEL;
	m_bih.biCompression = BI_RGB;
	m_bih.biSizeImage = m_dwByteSize;

	InitTimer();

	m_dLastFrameRefTime = GetTime();
	m_dStepTime = 0;

	m_vFpsAuxMean.resize(100);
	m_vFpsAuxMean.assign(m_vFpsAuxMean.size(), 1000.0);
	m_iFpsAuxIndex = 0;
	m_dFpsAuxSum = 1000.0 * m_vFpsAuxMean.size();
	std::clog << "Render manager initialized successfully" << std::endl;
}

void RenderMan::Shutdown()
{
	std::clog << "Shutting down render manager..." << std::endl;
	m_hWnd = NULL;
	m_iWidth = 0;
	m_iHeight = 0;
	m_dwByteSize = 0;
	
	m_vScreenBuffer.clear();
	
	m_vZBuffer.clear();

	m_vFpsAuxMean.clear();

	if (m_hDrawDib) DrawDibClose(m_hDrawDib);
	m_hDrawDib = NULL;
	std::clog << "Render manager shut down successfully" << std::endl;
}

void RenderMan::DrawFrame()
{
	UpdateTimer();

	ClearScreen();
	
	NoiseEffect();

	Slow_DrawLine(0, 0, 400, 400, _RGB(255,255,255));
	Slow_DrawLine(0, 0, 799, 599, _RGB(255,255,255));
	Slow_DrawLine(10, 400, 400, 50, _RGB(50,200,50));
	Slow_DrawLine(200, 10, 300, 500, _RGB(255,50,50));
	Slow_DrawLine(200, 300, 700, 301, _RGB(255,255,50));

	PrintTextInfo();
	
	HDC hDC = GetDC(m_hWnd);
	DrawDibDraw(m_hDrawDib, hDC, 0, 0, m_iWidth, m_iHeight, &m_bih, &m_vScreenBuffer.front(), 0, 0, m_iWidth, m_iHeight, 0); 
	ReleaseDC(m_hWnd, hDC);
}

void RenderMan::InitTimer()
{
	unsigned __int64 frequency;
	QueryPerformanceFrequency((LARGE_INTEGER*)&frequency);
	m_dFrequency = 1000.0 / (TIME)frequency;
}

TIME RenderMan::GetTime()
{
	unsigned __int64 current;
	QueryPerformanceCounter((LARGE_INTEGER*)&current);
	return (current * m_dFrequency);
}

UINT32 RenderMan::GetFPS()
{
	return m_iFPS;
}

void RenderMan::NoiseEffect()
{
	static TIME dTimeFromStart = 0;
	dTimeFromStart += m_dStepTime;
	static bool bApplyEffect = true;
	if (bApplyEffect)
	{
		UINT32 iNoiseReductionParam = (UINT32)(256.0 - (dTimeFromStart/5000.0) * 256.0);
		if (iNoiseReductionParam > 0)
		{
			UINT32* lpScreenBuffer = &m_vScreenBuffer.front();
			UINT32 iTotalPixels = m_iWidth*m_iHeight;

			for (UINT32 i=0; i<iTotalPixels; i++)
			{
				UINT32 randValue = fastrand()%iNoiseReductionParam;
				*(lpScreenBuffer + i) |= (randValue) | (randValue<<8) | (randValue<<16);
			}
		}
		else
		{
			bApplyEffect = false;
		}
	}
}

void RenderMan::ClearScreen()
{
	memset(&m_vScreenBuffer.front(), 0, m_vScreenBuffer.size()*_BYTES_PER_PIXEL);
	memset(&m_vZBuffer.front(), 0, m_vZBuffer.size()*_BYTES_PER_PIXEL);
}


void inline RenderMan::UpdateTimer()
{	
	m_dAbsoluteTime = GetTime();
	m_dStepTime = (m_dAbsoluteTime - m_dLastFrameRefTime);
	m_dLastFrameRefTime = m_dAbsoluteTime;
	m_dFpsAuxSum -= m_vFpsAuxMean[m_iFpsAuxIndex];
	m_dFpsAuxSum += m_dStepTime;
	m_vFpsAuxMean[m_iFpsAuxIndex] = m_dStepTime;
	m_iFpsAuxIndex = (m_iFpsAuxIndex + 1) % m_vFpsAuxMean.size();
	m_iFPS = (int)(1000.0 / (m_dFpsAuxSum / m_vFpsAuxMean.size()));
}

void RenderMan::PrintToBuffer(vector<UINT32> &vScrBuffer, UINT32 resX, UINT32 resY, UINT32 dstX, UINT32 dstY, LPSTR lpszText, _PIXEL textColor, LPTSTR lpszFontName, UINT32 fontSize, BOOL bBold, BOOL bItalic)
{
	// Check for valid bitmap
	if (lpszText != NULL)
	{
		// Draw text on the bitmap
		UINT32 iLen = (UINT32)strlen(lpszText);
		HDC hDC = ::GetDC(NULL);
		UINT32 iFontHeight = -MulDiv(fontSize, ::GetDeviceCaps(hDC, LOGPIXELSY), 72);
		UINT32 iWeight = FW_NORMAL;
		if (bBold)
			iWeight = FW_BOLD;
		HFONT hFont = ::CreateFont(iFontHeight, 0, 0, 0, iWeight, bItalic, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, lpszFontName);
		HFONT hOldFont = (HFONT)::SelectObject(hDC, hFont);
		SIZE sz;
		::GetTextExtentExPointA(hDC, lpszText, iLen, 0, NULL, NULL, &sz);
		GLYPHMETRICS gm;
		MAT2 m2 = {{0,1}, {0,0}, {0,0}, {0,1}};
		UINT32 iLetterOffset = 0;
		UINT32 iLineOffset = 0;
		UINT32 iSize = 4096;
		vector<BYTE> vFontBitmap;
		vFontBitmap.resize(iSize);
		for (UINT32 k=0; k<iLen; k++)
		{
			// Check for libe-break character
			if (lpszText[k] == '\n')
			{
				iLineOffset += sz.cy;
				iLetterOffset = 0;
			}
			else
			{
				// Check for valid letter
				if (lpszText[k] != ' ')
				{
					// Get font letter data
					::GetGlyphOutlineA(hDC, lpszText[k], GGO_GRAY8_BITMAP, &gm, iSize, &vFontBitmap.front(), &m2);

					// Calculate letter params
					UINT32 iVerticalSkip = sz.cy - gm.gmBlackBoxY + (gm.gmBlackBoxY-gm.gmptGlyphOrigin.y);
					UINT32 iHorizontalSkip = gm.gmptGlyphOrigin.x;
					UINT32 pitch = gm.gmBlackBoxX;
					while ((pitch & 3) != 0)
						pitch++;

					// Draw single letter
					UINT32 iVerticalOffset = 0;
					UINT32 cy = (UINT32)gm.gmBlackBoxY;
					UINT32 cx = (UINT32)gm.gmBlackBoxX;
					for (UINT32 i=0; i<cy; i++)
					{
						UINT32 iHorizontalOffset = 0;
						for (UINT32 j=0; j<cx; j++)
						{
							UINT32 index = iVerticalOffset + iHorizontalOffset;
							UINT32 dwTotalOffset = dstX+j+iLetterOffset+iHorizontalSkip + (resY-1-(dstY+i+iVerticalSkip+iLineOffset)) * resX;
							float pixelPower = (float)vFontBitmap[index] / 64.0f;
							vScrBuffer[dwTotalOffset] |= (DWORD)((float)textColor * pixelPower);

							// Increment horizontal offset
							iHorizontalOffset++;
						}

						// Increment vertical offset
						iVerticalOffset += pitch;
					}
				}

				// Increment letter horizontal offset
				iLetterOffset += gm.gmCellIncX;
			}
		}
		::SelectObject(hDC, hOldFont);
		::DeleteObject(hFont);
		::ReleaseDC(NULL, hDC);
	}
}

void RenderMan::PrintTextInfo()
{
	static TIME dRefTime = 0;
	if (m_dAbsoluteTime - dRefTime >= 1000.0)
	{
		ClearBuffer(m_vTextBuffer, m_iTextBufferWidth, m_iTextBufferHeight);
		dRefTime = m_dAbsoluteTime;
		char szFpsInfo[1024];
		UINT32 iFPS = GetFPS();
		if (iFPS > 1)
		{
			sprintf_s(szFpsInfo, "FPS = %d", iFPS);
		}
		else
		{
			sprintf_s(szFpsInfo, "Initializing...");
		}
		PrintToBuffer(m_vTextBuffer, m_iTextBufferWidth, m_iTextBufferHeight, 5, 5, szFpsInfo, _RGB(0,255,0), _T("Consolas"), 10, true, false);
	}
	PrintBufferAdditive(m_vTextBuffer, m_iTextBufferWidth, m_iTextBufferHeight);
}

void RenderMan::PrintBufferAdditive(const vector<UINT32> &vAddBuffer, UINT32 resX, UINT32 resY)
{
	UINT32* lpScreenBuffer = &m_vScreenBuffer.front();
	const UINT32* lpTextBuffer = (const UINT32*)&vAddBuffer.front();
	for (UINT32 j=0; j<resY; j++)
	{
		UINT32 iDestVerticalOffset = (m_iHeight-1)*m_iWidth - j*m_iWidth;
		UINT32 iOrigVerticalOffset = (resY-1)*resX - j*resX;
		for (UINT32 i=0; i<resX; i++)
		{			
			*(lpScreenBuffer + iDestVerticalOffset + i) |= *(lpTextBuffer + iOrigVerticalOffset + i);
		}
	}
}

void RenderMan::ClearBuffer(vector<UINT32> &vScrBuffer, UINT32 resX, UINT32 resY)
{
	memset(&vScrBuffer.front(), 0, vScrBuffer.size()*_BYTES_PER_PIXEL);
}